﻿bob.ns.setNs('bob.collections', {

    Enumerator: function (fnHasNext, fnGetNext, fnGetIndex) {
        if (!bob.utils.isFunction(fnHasNext)) {
            throw new Error('Wrong argument: fnHasNext.');
        }
        if (!bob.utils.isFunction(fnGetNext)) {
            throw new Error('Wrong argument: fnGetNext.');
        }
        if (!bob.utils.isFunction(fnGetIndex)) {
            throw new Error('Wrong argument: fnGetIndex.');
        }

        var self = this;
        var _hasNext = fnHasNext;
        var _getNext = fnGetNext;
        var _fnGetIndex = fnGetIndex;

        var _set_current = bob.prop.namedProp(this, 'current', undefined, true);
        var _set_index = bob.prop.namedProp(this, 'index', -1, true);

        this.next = function () {
            if (_hasNext()) {
                _set_current(_getNext());
                _set_index(_fnGetIndex());
                return true;
            }
            return false;
        };

    },

    arrayEnumerator: function (array) {
        if (!bob.utils.isArray(array)) {
            throw new Error('Wrong argument: array.');
        }

        var index = -1;
        var fnHasNext = function () {
            return index < (array.length - 1);
        };
        var fnGetNext = function () {
            return array[++index];
        };
        var fnGetIndex = function () {
            return index;
        };

        return new bob.collections.Enumerator(fnHasNext, fnGetNext, fnGetIndex);
    },

    stringEnumerator: function (str) {
        if (!bob.utils.isString(str)) {
            throw new Error('Wrog argument: str.');
        }

        var index = -1;
        var fnHasNext = function () {
            return index < (str.length - 1);
        };
        var fnGetNext = function () {
            return str.charAt(++index);
        };
        var fnGetIndex = function () {
            return index;
        };

        return new bob.collections.Enumerator(fnHasNext, fnGetNext, fnGetIndex);
    },

    objectEnumerator: function (obj) {
        if (!bob.utils.isObject(obj)) {
            throw new Error('Wrong argument: obj.');
        }

        var array = [];
        for (var member in obj) {
            array.push({ 'name': member, 'value': obj[member] });
        }

        return bob.collections.arrayEnumerator(array);
    },

    //v.2
    objectIndexerEnumerator: function (obj) {
        if (!bob.utils.isObject(obj)) {
            throw new Error('Wrong argument: obj.');
        }

        var array = [];
        for (var member in obj) {
            array.push({ 'name': member, 'value': obj[member] });
        }

        var index = -1;
        var fnHasNext = function () {
            return index < (array.length - 1);
        };
        var fnGetNext = function () {
            return array[++index].value;
        };
        var fnGetIndex = function () {
            return array[index].name;
        };

        return new bob.collections.Enumerator(fnHasNext, fnGetNext, fnGetIndex);
    },

    getEnumerator: function (objAny) {
        if (bob.utils.isArray(objAny)) {
            return bob.collections.arrayEnumerator(objAny);
        }
        if (bob.utils.isString(objAny)) {
            return bob.collections.stringEnumerator(objAny);
        }
        if (bob.utils.isObject(objAny)) {
            return bob.collections.objectIndexerEnumerator(objAny);
        }
        if (!bob.utils.isUndefinedOrNull(objAny)
            && bob.utils.isFunction(objAny.getEnumerator)) {
            return objAny.getEnumerator();
        }

        throw new Error('Wrong argument: objAny.');
    },

    //v.2
    CollectionCreator: function (fnAddItem, fnGetValue) {
        if (!bob.utils.isFunction(fnAddItem)) {
            throw new Error('Wrong argument: fnAddItem.');
        }
        if (!bob.utils.isFunction(fnGetValue)) {
            throw new Error('Wrong argument: fnGetValue.');
        }

        var self = this;
        var _fnAddItem = fnAddItem;
        var _fnGetValue = fnGetValue;

        this.addItem = function (index, item) {
            _fnAddItem(index, item);
        };

        this.getValue = function () {
            return _fnGetValue();
        };
    },

    //v.2
    arrayCreator: function (arrValue) {
        if (bob.utils.isUndefinedOrNull(arrValue)) {
            arrValue = [];
        }
        else if (!bob.utils.isArray(arrValue)) {
            throw new Error('Wrong argument: arrValue.');
        }

        var fnAddItem = function (index, item) {
            arrValue.push(item);
        };
        var fnGetValue = function () {
            return arrValue;
        };

        return new bob.collections.CollectionCreator(fnAddItem, fnGetValue);
    },

    //v.2
    stringCreator: function (sValue) {
        if (bob.utils.isUndefinedOrNull(sValue)) {
            sValue = '';
        }
        else if (!bob.utils.isString(sValue)) {
            throw new Error('Wrong argument: sValue.');
        }

        //string to char array.
        var value = sValue.split('');

        var fnAddItem = function (index, item) {
            value.push(item);
        };
        var fnGetValue = function () {
            return value.join('');
        };

        return new bob.collections.CollectionCreator(fnAddItem, fnGetValue);
    },

    //v.2
    objectCreator: function (objValue) {
        var value = bob.utils.ifUndefinedOrNull(objValue, {});

        var fnAddItem = function (index, item) {
            value[index] = item;
        };
        var fnGetValue = function () {
            return value;
        };

        return new bob.collections.CollectionCreator(fnAddItem, fnGetValue);
    },

    //v.2
    getCollectionCreator: function (objSource, initValue) {
        if (bob.utils.isArray(objSource)) {
            return bob.collections.arrayCreator(initValue);
        }
        if (bob.utils.isString(objSource)) {
            return bob.collections.stringCreator(initValue);
        }
        if (bob.utils.isObject(objSource)) {
            return bob.collections.objectCreator(initValue);
        }
        if (!bob.utils.isUndefinedOrNull(objSource)
            && bob.utils.isFunction(objSource.getCreator)) {
            return objSource.getCreator(initValue);
        }

        throw new Error('Wrong argument: objSource.');
    },

    //v.2
    ExtendedCollection: function (arr) {

        var self = this;
        var _arr = arr;

        //attach all functions from bob.collections.extensions.
        var transformFn = function (fn) {
            var result = function () {
                var newArr = fn.apply(_arr, arguments);
                return new bob.collections.ExtendedCollection(newArr);
            };
            return result;
        };
        var resultFn = function (fn) {
            var result = function () {
                return fn.apply(_arr, arguments);
            };
            return result;
        };

        var ext = bob.collections.extensions;

        self.equals = transformFn(ext.equals);
        self.clone = transformFn(ext.clone);
        self.any = resultFn(ext.any);
        self.all = resultFn(ext.all);
        self.forEach = transformFn(ext.forEach);
        self.firstOrDefault = resultFn(ext.firstOrDefault);
        self.lastOrDefault = resultFn(ext.lastOrDefault);
        self.select = transformFn(ext.select);
        self.where = transformFn(ext.where);
        self.except = transformFn(ext.except);
        self.atIndex = resultFn(ext.atIndex);
        self.combine = transformFn(ext.combine);
        self.count = resultFn(ext.count);
        self.toObject = transformFn(ext.toObject);
        self.toArray = transformFn(ext.toArray);
        self.groupBy = transformFn(ext.groupBy);
        self.aggregate = resultFn(ext.aggregate);
        self.sum = resultFn(ext.sum);
        self.avg = resultFn(ext.avg);
        self.min = resultFn(ext.min);
        self.max = resultFn(ext.max);
        self.indexOf = resultFn(ext.indexOf);
        self.recursiveWhere = transformFn(ext.recursiveWhere);

        //expose unwrapper.
        self.getValue = function () {
            return _arr;
        };
    },

    //v.2
    extend: function (arr) {
        return new bob.collections.ExtendedCollection(arr);
    }
});
